AddCSLuaFile("autorun/shared/StargateExtras.lua")
AddCSLuaFile("autorun/client/cl_stargateextras.lua")

include("autorun/shared/StargateExtras.lua")

SGE.overloaders = {}

function SGE:Initialize()
   if(timer.IsTimer("SGE")) then
      return
   end
   
   local thinkFunction = function()
      self:Think()
   end

   timer.Create("SGE", self.CYCLE_INTERVAL, 0, thinkFunction, nil)
   
   Msg("================================\n")
   Msg("==== Stargate Extras Loaded ====\n")
   Msg("========= version ", self.VERSION, " ========\n")
   Msg("================================\n")
   
   --[[
   for _, gate in pairs(ents.FindByClass("stargate*")) do
      self:MakeStargateUseEnergy(gate)
   end
   ]]---
end

-- Registers an entity with the system
-- This enables damage system support for the entity and enables other related 
function SGE:RegisterEntity(entity)
   self:Initialize()
   
   entity.isCombatSystemEnabled = self:RegisterWithDamageSystem(entity)
   
   if(entity:GetClass() == "gate_overloader") then
      self:RegisterOverloader(entity)
   end
end

-- Unregisters an entity with the system
function SGE:UnregisterEntity(entity)
   if(entity:GetClass() == "gate_overloader") then
      self:UnregisterOverloader(entity)
   end
end

-- Internal function (registers a stargate overloader)
function SGE:RegisterOverloader(overloader)
   table.insert(self.overloaders, overloader)
end

-- Internal function (unregisters a stargate overloader)
function SGE:UnregisterOverloader(overloader)
   for overloaderNum, registeredOverloader in ipairs(self.overloaders) do
      if(overloader == registeredOverloader) then
         table.remove(self.overloaders, overloaderNum)
         return true
      end
   end
   
   return false
end

function SGE:RegisterWithDamageSystem(entity)
   if(CombatDamageSystem) then
      CDS_Spawned(entity)
      return true
   elseif(gcombat) then
      gcombat.registerent(entity, entity:Health())
      return true
   else
      return false
   end
end

function SGE:Think()
   self:UpdateGateTemperatures()
end

function SGE:DrawGateHeatEffects(gate)
   self:TintGate(gate)

   if(gate.excessPower) then
      -- The amount of excess power required to destabalise the stargate
      local destabalisingExcessPower = 3 * (gate.excessPowerLimit / 4)
      
      if(gate.excessPower >= destabalisingExcessPower) then
         -- Chance of the gates flickering if excessPower is at least 3/4 of the limit
         if(math.random(0, gate.excessPower) >= destabalisingExcessPower) then
            if(gate.malfunction) then
               gate.malfunction:Fire("DoSpark","",0)
            end
            
            self:MakeGateFlicker(gate)
         end
      end
   end
end

function SGE:MakeGateFlicker(gate)
   local remoteGate = self:GetRemoteStargate(gate)
   
   if(remoteGate == nil || remoteGate:IsValid() == false) then
      return false
   end
   
   if(gate.Flicker) then     
      gate:Flicker()
      remoteGate:Flicker()
   elseif(gate.EventHorizon) then
      gate.EventHorizon:SetColor(150, 150, 150, 255)
      remoteGate.EventHorizon:SetColor(150, 150, 150, 255)
      
      local function resetColour(entity)
         if(entity && entity:IsValid()) then
            entity:SetColor(255, 255, 255, 255)
         end
      end
      
      timer.Simple(0.5, resetColour, gate.EventHorizon)
      timer.Simple(0.5, resetColour, remoteGate.EventHorizon)
   end
end

function SGE:UpdateGateTemperatures()
   for _, gate in pairs(ents.FindByClass("stargate*")) do
      local shouldCoolGate = true
   
      for _, overloader in pairs(self.overloaders) do
         if(overloader.remoteGate == gate) then
            shouldCoolGate = false
         end
      end
      
      if(shouldCoolGate) then
         self:CoolGate(gate)
      end
      
      self:DrawGateHeatEffects(gate)
      self:CauseHeatDamage(gate)
   end
end

function SGE:CoolGate(gate)
   if(gate == nil) then
      error("Gate passed to CoolGate(gate) cannot be nil.\n")
   elseif(gate.excessPower == nil || gate.excessPower <= 0) then
      return false
   end

   -- The amount of energy lost as the gate cools
   local energyDissipated = math.min(self.COOLING_PER_CYCLE, gate.excessPower)
   
   -- Dissipate some of the excess power
   gate.excessPower = gate.excessPower - energyDissipated
   
   if(StarGate.HasResourceDistribution) then
      -- Reduce the amount of energy the gate can store by the amount dissipated
      -- This will result in the gate returning to its original energy capacity once it has completely cooled
      StarGate.WireRD.AddResource(gate, "energy", self:GetStargateEnergyCapacity(gate) - energyDissipated)
   end
   
   return true
end

function SGE:TintGate(gate)
   if(gate == nil || gate:IsValid() == false) then
      error("Gate passed to SGE:TintGate was not valid.\n")
   elseif(gate.excessPower == nil) then
      return
   end
   
   if(gate.excessPower < 0) then
      gate.excessPower = 0
   end

   local tintAmount = 255 * (gate.excessPower / gate.excessPowerLimit)
   
   gate:SetColor(255, 255 - tintAmount, 255 - tintAmount, 255)
   
   if(gate.chevron7) then
      gate.chevron7:SetColor(255, 255 - tintAmount, 255 - tintAmount, 255)
   end
   
   local iris = self:GetIris(gate)
   
   if(self:IsEntityValid(iris)) then
      tintAmount = math.min(tintAmount * 2, 128)
      
      --Msg("Setting ", iris, " colour(255, ", 255 - tintAmount, ", ", 255 - tintAmount, ")\n")
      iris:SetColor(255, 255 - tintAmount, 255 - tintAmount, 255)
   end
end

function SGE:EmitHeat(pos, damage, radius, inflictor)
   if(CombatDamageSystem) then
      cds_heatpos(pos, damage, radius)
      return true
   elseif(gcombat) then
      gcombat.emitheat(pos, radius, damage, inflictor)
      return true
   else
      return false
   end
end

function SGE:HeatEntity(entity, damage, radius, inflictor)
   if(self:EmitHeat(entity:GetPos(), damage, radius, inflictor)) then
      return true
   else
      if(entity.burnInflictor == nil) then
         local burnInflictor = ents.Create("point_hurt")
         burnInflictor:SetOwner(inflictor)
         burnInflictor:SetPos(self:GetEntityCentre(entity))
         burnInflictor:SetKeyValue("DamageDelay", 0.2)
         burnInflictor:SetKeyValue("DamageType", 8) -- Burn damage
         
         burnInflictor:Spawn()
         burnInflictor:Activate()
         burnInflictor:SetParent(entity)
         
         entity.burnInflictor = burnInflictor
      end
      
      entity.burnInflictor:SetKeyValue("DamageRadius", radius)
      entity.burnInflictor:SetKeyValue("Damage", damage)
   end
end

function SGE:CoolEntity(entity)
   if(entity.burnInflictor) then
      entity.burnInflictor:Remove()
      entity.burnInflictor = nil
   end
end

function SGE:CauseHeatDamage(gate)
   if(gate == nil) then
      error("Gate passed to CauseHeatDamage(gate) cannot be nil.\n")
   elseif(gate.excessPower == nil) then
      return false
   end

   if(gate.excessPower > gate.excessPowerLimit / 2) then  
      -- Make gate cause damage to nearby players due to extreme heat
      local heatDamage = math.Round(2 * (gate.excessPower / gate.excessPowerLimit))
      local heatRadius = 500 * (gate.excessPower / gate.excessPowerLimit)
      local overloaderOwner = gate.overloader:GetVar("Owner", 
                                                     gate.overloader:GetOwner())
      
      self:HeatEntity(gate, heatDamage, heatRadius, overloaderOwner)
      
      if(gate.excessPower > 3 * (gate.excessPowerLimit / 4)) then
         for _, entity in pairs(ents.FindInSphere(self:GetEntityCentre(gate), heatRadius)) do
            if(entity ~= gate) then
               entity:Ignite(2, 25)
            end
         end
      end
      
      return true
   else
      self:CoolEntity(gate)
      return false
   end
end

-- Jams the given gate, preventing it from closing the current connection
-- Also overrides USE key behaviour on the gate so that this key instead toggles the iris
-- Returns: whether the gate could be jammed
function SGE:JamRemoteGate(gate)
   if(gate == nil || gate:IsValid() == false) then
      error("The stargate passed to JamRemoteGate(gate) is not valid.\n")
      return false
   elseif(self:IsStargateOpen(gate) == false || 
          self:IsStargateOutbound(gate)) then
      error("The stargate passed to JamRemoteGate(gate) does not have an outbound wormhole open.\n")
      return false
   elseif(gate.jammed == true) then
      return true
   end
   
   function DummyFunction(...)
      return false
   end
   
   -- Override remote gate's functions to prevent shutdown
   
   gate.backups = {}
   
   gate.backups.AcceptInput = gate.AcceptInput
   gate.AcceptInput = DummyFunction
   
   -- Do not simply disable pressing the 'use' key on a gate - instead make this toggle the iris (the one function of the remote gate that should not be disabled)
   gate.backups.Use = gate.Use
   gate.Use = function()
      if(gate.Iris) then -- If new iris exists on gate
         gate.Iris:Toggle()
      elseif(gate.irisclosed == true) then -- If old gate and iris is closed
         gate:OpenIris(true)
      elseif(gate.irisclosed == false) then -- If old gate and iris is open
         gate:OpenIris(false)
      end
   end
   
   gate.backups.EmergencyShutdown = gate.EmergencyShutdown
   gate.EmergencyShutdown = DummyFunction
   
   gate.backups.DeactivateStargate = gate.DeactivateStargate
   gate.DeactivateStargate = DummyFunction
   
   gate.backups.Close = gate.Close
   gate.Close = DummyFunction
   
   gate.backups.ActivateStargate = gate.ActivateStargate
   gate.ActivateStargate = DummyFunction
   
   gate.backups.Open = gate.Open
   gate.Open = DummyFunction
   
   gate.backups.auto_close = gate.auto_close
   gate.auto_close = false
   
   gate.jammed = true
   
   local localGate = self:GetRemoteStargate(gate)
   
   localGate.backups = {}
   
   -- Override local gate's functions to un-jam the remote gate when the connection is closed locally
   
   localGate.backups.AcceptInput = localGate.AcceptInput
   localGate.AcceptInput = function(name, activator, caller)
      if(activator == "Use" || name == nil) then
         return
      end
      
      self:UnJamGate(localGate)
      localGate:AcceptInput(name, activator, caller)
   end
   
   -- For compatibility with UnJamGate
   localGate.backups.Use = localGate.Use
   
   localGate.backups.EmergencyShutdown = localGate.EmergencyShutdown
   localGate.EmergencyShutdown = DummyFunction
   
   localGate.backups.DeactivateStargate = localGate.DeactivateStargate
   localGate.DeactivateStargate = DummyFunction
   
   localGate.backups.ActivateStargate = localGate.ActivateStargate
   localGate.ActivateStargate = function(inbound, fast)
      self:UnJamGate(self:GetRemoteStargate(localGate))
      localGate:ActivateStargate(inbound, fast)
   end
   
   localGate.backups.Open = localGate.Open
   localGate.Open = DummyFunction
   
   localGate.backups.Close = localGate.Close
   localGate.Close = DummyFunction
   
   localGate.backups.auto_close = localGate.auto_close
   localGate.auto_close = false
   
   localGate.jammed = true

   return true
end

function SGE:UnJamGate(gate)
   if(gate == nil) then
      error("The stargate passed to UnJamGate(gate) cannot be nil.\n")
      return false
   elseif(gate:IsValid() == false) then
      return false
   elseif(gate.jammed ~= true || gate.backups == nil) then
      return true
   end

   gate.AcceptInput = gate.backups.AcceptInput
   gate.Use = gate.backups.Use
   gate.EmergencyShutdown = gate.backups.EmergencyShutdown
   gate.DeactivateStargate = gate.backups.DeactivateStargate
   gate.Close = gate.backups.Close
   gate.ActivateStargate = gate.backups.ActivateStargate
   gate.Open = gate.backups.Open
   gate.auto_close = gate.backups.auto_close
   
   gate.jammed = false
   
   return self:UnJamGate(self:GetRemoteStargate(gate))
end

function SGE:JamDHD(dhd, duration)
   if(dhd == nil) then
      error("The DHD passed to JamDHD(dhd) cannot be nil.\n")
      return false
   elseif(dhd:IsValid() == false) then
      return false
   end
   
   if(dhd.SetBusy) then
      dhd:SetBusy(duration)
   else   
      dhd.busy = true
   end
   
   return true
end

function SGE:UnJamDHD(dhd)
   if(dhd == nil) then
      error("The DHD passed to UnJamDHD(dhd) cannot be nil.\n")
      return false
   elseif(dhd:IsValid() == false) then
      return false
   end

   dhd.busy = false
   return true
end

function SGE:GetGateMarker(gate)
   if(gate == nil) then
      error("Gate passed to GetGateMarker(gate) cannot be nil.\n")
   end
   
   if(gate.centreMarker == nil) then
      gate.centreMarker = ents.Create("info_target")
      gate.centreMarker:SetPos(self:GetEntityCentre(gate))
      gate.centreMarker:SetName("GateMarker"..gate:EntIndex())
      gate.centreMarker:Spawn()
      gate.centreMarker:SetParent(gate)
   end
   
   return gate.centreMarker
end

function SGE:GetStargateEnergyCapacity(gate)
   if(gate.HasRD) then
      return gate:GetUnitCapacity("energy")
   else
      return gate.capacity
   end
end

function SGE:SetStargateEnergyCapacity(gate, capacity)
   if(gate.HasRD) then
      gate:AddResource("energy", capacity)
   end
   
   gate.capacity = capacity
end

function SGE:MakeStargateUseEnergy(gate)
   if(gate.IsStargate ~= true) then
      return
   end

   local gateCapacity = self.STARGATE_DEFAULT_ENERGY_CAPACITY
   local energyDrain = self.STARGATE_DEFAULT_ENERGY_DRAIN
   local rechargeTime = 300
   
   self:SetStargateEnergyCapacity(gate, gateCapacity)
   
   gate.backups.Think = gate.Think
   gate.Think = function()
      if(self:IsStargateOutbound(gate)) then
         local energyConsumed = 0
      
         if(StarGate.HasResourceDistribution) then
            energyConsumed = StarGate.WireRD.ConsumeResource(gate, "energy", energyDrain)
         else
            energyConsumed = math.min(energyDrain, gate.energy)
            gate.energy = gate.energy - energyConsumed
         end
         
         if(energyConsumed < energyDrain) then
            gate:DeactivateStargate()
         end
      elseif(!StarGate.HasResourceDistribution) then
         gate.energy = math.min(gate.energy + (gateCapacity * self.CYCLE_INTERVAL / rechargeTime), gateCapacity)
      end
   
      gate.backups.Think(gate)
      gate:SetNextThink(CurTime() + self.CYCLE_INTERVAL)
   end
   
   gate.backups.ActivateStargate = gate.ActivateStargate
   gate.ActivateStargate = function(...)
      gate.backups.ActivateStargate(gate, ...)
      
      if((StarGate.HasResourceDistribution && StarGate.WireRD.GetResource(gate, "energy") <= 0) ||
         (gate.energy && gate.energy <= 0)) then
         gate:EmergencyShutdown()
      end
   end
end

-- Credit to aVoN for this function originally
-- Has since been cleaned up and modified to work with both old and new gates
function SGE:DestroyStargate(gate)
	if(!self:IsStargateDialling(gate) && 
      (gate.last_vaporize == nil || gate.last_vaporize + 10 < CurTime())) then
		gate.last_vaporize = CurTime()
      
		if(gate.use_nuke == nil || gate.use_nuke == true) then 
         local nuke = ents.Create("gate_nuke")
         
         if(nuke and nuke:IsValid()) then
            nuke:Setup(self:GetEntityCentre(gate), 100)
            nuke:Spawn()
            nuke:Activate()
         end
      else
			local gatePos = self:GetEntityCentre(gate)
         
         local fx = EffectData()
   		fx:SetOrigin(gatePos)
   		util.Effect("Unstable_Explosion", fx)
         
         util.BlastDamage(gate.Entity, gate, gatePos, 2048, 1000)
		end
      
    self:UnJamGate(gate)
    self:GetRemoteStargate(gate):DeactivateStargate(true)
		gate:DeactivateStargate(true)
      
		if(self:IsProtectedByGateSpawner(gate) == false) then
			for _, dhd in pairs(gate:FindDHD(true)) do
				if(self:IsProtectedByGateSpawner(dhd) == false) then
					dhd:Remove()
				end
			end
			
         timer.Simple(1, function() gate:Remove() end, nil)
		else
         gate.excessPower = 0
         gate.isOverloading = false -- Reset this so that the gate can be overloaded again in the future
		end
	end
end

function SGE:GetIris(gate)
   if(gate.iris) then
      return gate.iris
   elseif(self:IsEntityValid(gate.Iris)) then
      return gate.Iris
   end
   
   for _, entity in pairs(ents.FindInSphere(gate:GetPos(), 10)) do
      if(entity.IsIris) then
         return entity
      end
   end
   
   return nil
end

function SGE:IsIrisClosed(gate)
   return gate.irisclosed == true || (gate.IsBlocked && gate:IsBlocked(true) == true)
end

--[[
function SGE:SpawnGateNuke(ent, scale) -- ent should be the ent that is spawning it eg. The naquadah bomb or the gate.
	local pos = self:GetEntityCentre(ent)
	local boundrad = ent:BoundingRadius()
	local contained = false
	local shield = null
	local spos = null
	local rad = null
	
	-- Check if the nuke is being spawned inside a containment shield and is completly inside.
	for k,v in pairs(ents.FindByClass("shield")) do
		if v:Isvalid() and v:GetNWBool("containment", false) and not v:GetNWBool("depleted", false) then
			local radius = v:GetNWInt("size", 0)
			if v:GetPos():Distance(pos)+boundrad < radius then
					spos = 
					contained = true
					shield = v
					rad = radius
				end
			end
		end
	end

	-- If it's not inside just spawn it normally
	if not contained then
		local nuke = ents.Create("gate_nuke")
		nuke:Setup(pos, scale)	
		nuke:Spawn()
		nuke:Activate()
	-- Do something else
	else	
		for k,v in pairs(ents.FindInSphere(shield:GetPos(), rad))
			if v:IsValid() then
				local phys = v:GetPhysicsObject()
				if phys:IsValid() then
					v:Remove()
				end
			end
		end
		
		for i=1,15 do
			timer.Simple(0.3*i, function() 
												

						local dgmmul = (dir:Length()*100/scale)^-2
						util.BlastDamage(self.Entity, self.Entity, entpos[k], 8, 2e6*(20-self.Rel)*dgmmul)
]]--